package com.yxx.service.Impl;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yxx.constant.CommonConstant;
import com.yxx.constant.RedisConstant;
import com.yxx.entity.User;
import com.yxx.entity.UserRole;
import com.yxx.entityModel.vo.Result.PageResult;
import com.yxx.entityModel.vo.query.OnlineUserQuery;
import com.yxx.entityModel.vo.query.UserQuery;
import com.yxx.entityModel.vo.request.DisableRequest;
import com.yxx.entityModel.vo.request.EmailRequest;
import com.yxx.entityModel.vo.request.PasswordRequest;
import com.yxx.entityModel.vo.request.UserRoleRequest;
import com.yxx.entityModel.vo.response.*;
import com.yxx.enums.FilePathEnum;
import com.yxx.mapper.MenuMapper;
import com.yxx.mapper.RoleMapper;
import com.yxx.mapper.UserMapper;
import com.yxx.mapper.UserRoleMapper;
import com.yxx.service.RedisService;
import com.yxx.service.UserService;
import com.yxx.strategy.context.UploadStrategyContext;
import com.yxx.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * @author yuxiangxun
 * @date 2025/1/21
 * @apiNote
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{

    @Resource
    private UserMapper userMapper;

    @Resource
    private MenuMapper menuMapper;

    @Resource
    private RedisService redisService;

    @Resource
    private UploadStrategyContext uploadStrategyContext;

    @Resource
    private RoleMapper roleMapper;

    @Resource
    private UserRoleMapper userRoleMapper;

    /**
     * 获取后台登录用户信息
     * @return
     */
    @Override
    public UserBackInfoResponse getUserBackInfo() {
        // 通过 sa-Token 工具包登录校验（权限）后的用户信息中提取userId
        Integer userId = StpUtil.getLoginIdAsInt();
        log.info("查看userId:{}",userId);
        // 如果 userId 为空，则抛出异常
        if(ObjectUtil.isNull(userId)){
            throw new RuntimeException("userId为空");
        }
        // 通过 userId 查询用户信息
        User user = userMapper.selectOne(
                new LambdaQueryWrapper<User>()
                        .select(User::getAvatar)
                        .eq(User::getId,userId)
        );
        log.info("通过userId查询到的用户信息为：{}",user);
        log.info("======================================================================");

        // 通过 sa-Token 工具包获取用户角色
        List<String> roleIdList = StpUtil.getRoleList();

        log.info("roleIdList:{}",roleIdList);

        // 用户权限列表 去除列表中的重复权限参数  过滤掉空值
        List<String> permissionList = StpUtil.getPermissionList()
                .stream()
                .filter(string -> !string.isEmpty())
                .distinct()
                .collect(Collectors.toList());

        log.info("permissionList:{}",permissionList);

        // 返回响应登录用户信息【用户(id 头像)/角色列表/权限信息】
        return UserBackInfoResponse.builder()
                .id(userId)
                .avatar(user.getAvatar())
                .roleList(roleIdList)
                .permissionList(permissionList)
                .build();

    }

    /**
     * 获取后台用户登录菜单
     * @return
     */
    @Override
    public List<RouterResponse> getUserMenu() {
        // 查询用户菜单 根据校验完后的登录用户id进行查询用户菜单
        List<UserMenuResponse> userMenuResponseList = menuMapper.selectMenuByUserId(StpUtil.getLoginIdAsInt());
        log.info("用户菜单列表UserMenuResponse为：{}",userMenuResponseList);
        // 递归生成路由 父菜单id parentId 为 0
        return recurRoutes(CommonConstant.PARENT_ID,userMenuResponseList);
    }

    /**
     * 修改管理员密码
     * @param passwordRequest
     */
    @Override
    public void updateAdminPassword(PasswordRequest passwordRequest) {
        // 通过 Sa-Token 中的StpUtil 获取当前登录用户的id
        int userId = StpUtil.getLoginIdAsInt();
        // 根据 userId 查询管理员信息
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getId, userId));
        // 用户（管理员）不存在，断言
        Assert.notNull(user, "用户不存在");
        // 查询出来的管理员信息中的旧密码（数据库中） 与 请求传递过来的旧密码进行比较（使用自定义的SecurityUtils工具包中的checkPw()方法） 不正确就抛出”旧密码验证不通过“
        Assert.isTrue(SecurityUtils.checkPassword(user.getPassword(), passwordRequest.getOldPassword()), "旧密码验证不通过");
        // 正确就修改密码（数据库） 用sha256加密新密码后更新数据库中用户（管理员）信息
        String newPassword = SecurityUtils.sha256Encrypt(passwordRequest.getNewPassword());
        user.setPassword(newPassword);
        userMapper.updateById(user);
    }

    /**
     * 获取登录用户信息
     * @return
     */
    @Override
    public UserInfoResponse getUserInfo() {
        // 通过 Sa-Token 中的 StpUtil 获取当前登录用户的id
        int userId = StpUtil.getLoginIdAsInt();
        // 根据当前登录用户的id查询用户信息
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .select(User::getAvatar, User::getUsername, User::getEmail, User::getIntro, User::getWebSite, User::getLoginType)
                .eq(User::getId, userId));
        // 封装 UserInfoResponse 并返回
        return UserInfoResponse.builder()
                .id(userId)
                .intro(user.getIntro())
                .avatar(user.getAvatar())
                .username(user.getUsername())
                .webSite(user.getWebSite())
                .email(user.getEmail())
                .loginType(user.getLoginType())
                .articleLikeSet(redisService.getSet(RedisConstant.USER_ARTICLE_LIKE + userId))
                .commentLikeSet(redisService.getSet(RedisConstant.USER_COMMENT_LIKE + userId))
                .talkLikeSet(redisService.getSet(RedisConstant.USER_TALK_LIKE + userId))
                .build();
    }

    /**
     * 修改用户邮箱
     * @param emailRequest
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUserEmail(EmailRequest emailRequest) {
        // 校验验证码
        verifyCode(emailRequest.getEmail(),emailRequest.getCode());
        User newUser = User.builder().id(StpUtil.getLoginIdAsInt()).email(emailRequest.getEmail()).build();
        userMapper.updateById(newUser);
    }

    /**
     * 校验验证码
     *
     * @param username 用户名
     * @param code     验证码
     */
    public void verifyCode(String username, String code) {
        String sysCode = redisService.getObject(RedisConstant.CODE_KEY + username);
        Assert.notBlank(sysCode, "验证码未发送或已过期！");
        Assert.isTrue(sysCode.equals(code), "验证码错误，请重新输入！");
    }

    /**
     * 修改用户头像
     * @param file
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String updateUerAvatar(MultipartFile file) {
        // 通过上传策略模式获取上传的头像路径
        String avatar = uploadStrategyContext.executeUploadStrategy(file, FilePathEnum.AVATAR.getPath());
        // 更新用户头像 (先封装用户id和用户新头像)
        User user = User.builder()
                .id(StpUtil.getLoginIdAsInt())
                .avatar(avatar)
                .build();
        // 更新用户信息
        userMapper.updateById(user);
        return avatar;
    }

    /**
     * 查看用户角色列表
     * @return
     */
    @Override
    public List<UserRoleResponse> userRoleDTOList() {
        // 查看用户角色列表
        return roleMapper.selectUserRoleList();
    }

    /**
     * 查看后台用户列表（分页查询）
     * @param userQuery
     * @return
     */
    @Override
    public PageResult<UserBackResponse> userBackListVOList(UserQuery userQuery) {
        // 查询后台用户数量
        Long count  = userMapper.selectUserCount(userQuery);
        // 如果后台没有用户的话，new 一个分页结果对象并返回
        if(count <= 0){
            return new PageResult<>();
        }
        // 后台有用户，查询后台用户信息列表
        List<UserBackResponse> userBackResponseList = userMapper.selectUserList(userQuery);
        // 将后台用户信息列表封装到分页结果对象并返回
        return new PageResult<>(userBackResponseList,count);
    }

    /**
     * 修改用户信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUser(UserRoleRequest userRoleRequest) {
        // 修改用户信息  根据前端传递过来的用户id、昵称创建新的User对象  然后根据新的User对象更新数据库中的用户信息
        User newUser = User.builder()
                .id(userRoleRequest.getId())
                .nickname(userRoleRequest.getNickname())
                .build();
        baseMapper.updateById(newUser);
        // 删除用户角色  根据用户id删除用户角色表中对应用户的信息
        userRoleMapper.delete(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId,userRoleRequest.getId()));
        // 重新添加用户角色
        userRoleMapper.insertUserRole(userRoleRequest.getId(),userRoleRequest.getRoleIdList());
        // 删除Redis缓存中的角色  首先获取获取指定账号id的Session,false为：如果Session尚未创建，不新建并返回
        SaSession sessionByLoginId = StpUtil.getSessionByLoginId(userRoleRequest.getId(), false);
        Optional.ofNullable(sessionByLoginId).ifPresent(saSession -> saSession.delete("Role_List"));
    }

    /**
     * 修改用户状态
     * @param disableRequest
     */
    @Override
    public void updateUserStatus(DisableRequest disableRequest) {
        // 根性用户状态，创建一个新的User对象，然后根据新的User对象更新数据库中的用户信息
        User newUser = User.builder()
                .id(disableRequest.getId())
                .isDisable(disableRequest.getIsDisable())
                .build();
        userMapper.updateById(newUser);
        if(disableRequest.getIsDisable().equals(CommonConstant.TRUE)){
            // 先踢下线
            StpUtil.logout(disableRequest.getId());
            // 再封禁账号
            StpUtil.disable(disableRequest.getId(),86400);
        }else{
            // 解除账号
            StpUtil.untieDisable(disableRequest.getId());
        }
    }

    /**
     * 查看在线用户列表（分页查询）
     * @param onlineUserQuery
     * @return
     */
    @Override
    public PageResult<OnlineUserResponse> onlineUserList(OnlineUserQuery onlineUserQuery) {
        // 查询所有的会话token
        List<String> tokenList = StpUtil.searchTokenSessionId("", 0, -1, false);
        List<OnlineUserResponse> onlineUserResponseList = tokenList.stream()
                .map(token -> {
                    // 通过每一个的会话token 获取到 每一个的 saSession 对象   获取 tokenSession
                    SaSession sessionByLoginId = StpUtil.getSessionByLoginId(token);
                    // 返回每一个会话对象对应的 OnlineUserResponse 对象
                    return (OnlineUserResponse) sessionByLoginId.get(CommonConstant.ONLINE_USER);
                })
                .filter(onlineUserResponse -> StringUtils.isEmpty(onlineUserQuery.getKeyword()) || onlineUserResponse.getNickname().contains(onlineUserQuery.getKeyword()))
                .sorted(Comparator.comparing(OnlineUserResponse::getLoginTime).reversed())
                .collect(Collectors.toList());
        // 执行分页
        int fromIndex = onlineUserQuery.getCurrent();
        int size = onlineUserQuery.getSize();
        int toIndex = onlineUserResponseList.size() - fromIndex > size ? fromIndex + size : onlineUserResponseList.size();
        List<OnlineUserResponse> onlineUserList = onlineUserResponseList.subList(fromIndex, toIndex);
        return new PageResult<>(onlineUserList, (long)onlineUserResponseList.size());
    }

    /**
     * 下线用户
     * @param token
     */
    @Override
    public void kickOutUser(String token) {
        StpUtil.logoutByTokenValue(token);
    }

    /**
     * 递归生成路由列表
     * 构建树形结构的路由列表
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return 路由列表
     */
    private List<RouterResponse> recurRoutes(Integer parentId, List<UserMenuResponse> menuList) {
        List<RouterResponse> list = new ArrayList<>();
        Optional.ofNullable(menuList).ifPresent(menus -> menus.stream()
                .filter(menu -> menu.getParentId().equals(parentId))
                .forEach(menu -> {
                    RouterResponse routeVO = new RouterResponse();
                    routeVO.setName(menu.getMenuName());
                    routeVO.setPath(getRouterPath(menu));
                    routeVO.setComponent(getComponent(menu));
                    routeVO.setMeta(MetaResponse.builder()
                            .title(menu.getMenuName())
                            .icon(menu.getIcon())
                            .hidden(menu.getIsHidden().equals(CommonConstant.TRUE))
                            .build());
                    if (menu.getMenuType().equals(CommonConstant.TYPE_DIR)) {
                        List<RouterResponse> children = recurRoutes(menu.getId(), menuList);
                        if (CollectionUtil.isNotEmpty(children)) {
                            routeVO.setAlwaysShow(true);
                            routeVO.setRedirect("noRedirect");
                        }
                        routeVO.setChildren(children);
                    } else if (isMenuFrame(menu)) {
                        routeVO.setMeta(null);
                        List<RouterResponse> childrenList = new ArrayList<>();
                        RouterResponse children = new RouterResponse();
                        children.setName(menu.getMenuName());
                        children.setPath(menu.getPath());
                        children.setComponent(menu.getComponent());
                        children.setMeta(MetaResponse.builder()
                                .title(menu.getMenuName())
                                .icon(menu.getIcon())
                                .hidden(menu.getIsHidden().equals(CommonConstant.TRUE))
                                .build());
                        childrenList.add(children);
                        routeVO.setChildren(childrenList);
                    }
                    list.add(routeVO);
                }));
        return list;
    }

    /**
     * 获取路由地址
     *
     * @param menu 菜单信息
     * @return 路由地址
     */
    public String getRouterPath(UserMenuResponse menu) {
        String routerPath = menu.getPath();
        // 一级目录  fix:修复无法通过菜单跳转指定菜单的问题，之前逻辑出现错误，现在改为逻辑如下：
        // 一级目录  菜单的父id == 系统的最父类id 且 菜单类型为 M （目录）
        if (menu.getParentId().equals(CommonConstant.PARENT_ID) && CommonConstant.TYPE_DIR.equals(menu.getMenuType())) {
            routerPath = "/" + menu.getPath();
        }
        // 一级菜单
        else if (isMenuFrame(menu)) {
            routerPath = "/";
        }
        return routerPath;
    }

    /**
     * 获取组件信息
     *
     * @param menu 菜单信息
     * @return 组件信息
     */
    public String getComponent(UserMenuResponse menu) {
        String component = CommonConstant.LAYOUT;
        if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) {
            component = menu.getComponent();
        } else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) {
            component = CommonConstant.PARENT_VIEW;
        }
        return component;
    }

    /**
     * 是否为菜单内部跳转
     *
     * @param menu 菜单信息
     * @return 结果
     */
    public boolean isMenuFrame(UserMenuResponse menu) {
        return menu.getParentId().equals(CommonConstant.PARENT_ID) && CommonConstant.TYPE_MENU.equals(menu.getMenuType());
    }

    /**
     * 是否为parent_view组件
     *
     * @param menu 菜单信息
     * @return 结果
     */
    public boolean isParentView(UserMenuResponse menu) {
        return !menu.getParentId().equals(CommonConstant.PARENT_ID) && CommonConstant.TYPE_DIR.equals(menu.getMenuType());
    }

}
